package com.sxw.user.service.impl;

import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sxw.dto.LoginDto;
import com.sxw.dto.PlayerSessionDto;
import com.sxw.user.domain.SysUser;
import com.sxw.user.mapper.SysUserMapper;
import com.sxw.user.service.SysUserService;
import com.sxw.utils.JwtTokenProvider;
import com.sxw.utils.R;
import com.sxw.utils.RedisUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    private final RedisUtils redisUtils;
    private final PasswordEncoder passwordEncoder;
    private final JwtTokenProvider jwtUtil;
    
    // 用户信息在Redis中的过期时间（秒）
    private static final long USER_CACHE_EXPIRE = 60 * 30; // 30分钟
    // 用户信息在Redis中的key前缀
    private static final String USER_CACHE_KEY_PREFIX = "user:info:";
    // 用户会话在Redis中的key前缀
    private static final String USER_SESSION_KEY_PREFIX = "player:session:";
    // 分布式锁的key前缀
    private static final String USER_CACHE_LOCK_KEY_PREFIX = "user:info:lock:";

    @Override
    public R<String> login(LoginDto loginDto) {
        String token = "";
        SysUser sysUser = null;
        String loginName = loginDto.getName();
        if("1".equals(loginDto.getLoginType())){
            // 用户密码登入
            // 1. 先从Redis中获取用户信息
            String userCacheKey = USER_CACHE_KEY_PREFIX + loginName;
            Object cachedUser = redisUtils.get(userCacheKey);
            
            if (cachedUser != null) {
                // Redis中存在用户信息
                sysUser = JSONObject.parseObject(cachedUser.toString(), SysUser.class);
                log.debug("从Redis中获取用户信息: {}", loginName);
            } else {
                // Redis中不存在，使用分布式锁查询数据库
                sysUser = getUserFromDatabaseWithLock(userCacheKey, "name", loginName, 0);
            }
            
            if (sysUser == null || !passwordEncoder.matches(loginDto.getPwd(), sysUser.getPwd())) {
                return R.fail("用户或密码错误");
            }
        }else if ("2".equals(loginDto.getLoginType())) {
            // 手机验证码登入
            String cap = redisUtils.getString(loginName);
            if(cap != null && cap.equals(loginDto.getCaptcha())){
                // redis验证码验证是否匹配
                // 先从Redis中获取用户信息
                String userCacheKey = USER_CACHE_KEY_PREFIX + "phone:" + loginName;
                Object cachedUser = redisUtils.get(userCacheKey);
                
                if (cachedUser != null) {
                    // Redis中存在用户信息
                    sysUser = JSONObject.parseObject(cachedUser.toString(), SysUser.class);
                    log.debug("从Redis中获取手机号登录用户信息: {}", loginName);
                } else {
                    // Redis中不存在，使用分布式锁查询数据库
                    sysUser = getUserFromDatabaseWithLock(userCacheKey, "phone", loginName, 0);
                }
            }else {
                return R.fail("验证码错误");
            }
        }else if ("3".equals(loginDto.getLoginType())) {
            // 邮箱验证码登入
            String cap = redisUtils.getString(loginName);
            if(cap != null && cap.equals(loginDto.getCaptcha())){
                // redis验证码验证是否匹配
                // 先从Redis中获取用户信息
                String userCacheKey = USER_CACHE_KEY_PREFIX + "email:" + loginName;
                Object cachedUser = redisUtils.get(userCacheKey);
                
                if (cachedUser != null) {
                    // Redis中存在用户信息
                    sysUser = JSONObject.parseObject(cachedUser.toString(), SysUser.class);
                    log.debug("从Redis中获取邮箱登录用户信息: {}", loginName);
                } else {
                    // Redis中不存在，使用分布式锁查询数据库
                    sysUser = getUserFromDatabaseWithLock(userCacheKey, "email", loginName, 0);
                }
            }else {
                return R.fail("验证码错误");
            }
        }
        
        // 2. 检查用户是否已经登录，如果已登录则返回现有token
        String sessionKey = USER_SESSION_KEY_PREFIX + loginName;
        Object sessionObj = redisUtils.get(sessionKey);
        if (sessionObj != null) {
            PlayerSessionDto playerSession = JSONObject.parseObject(sessionObj.toString(), PlayerSessionDto.class);
            if (playerSession != null && playerSession.getToken() != null) {
                // 如果存在有效的token，直接返回
                log.debug("用户已登录，返回现有token: {}", playerSession.getName());
                return R.ok(playerSession.getToken());
            }
        }
        
        // 3. 生成JWT Token (只存储用户ID)
        if (sysUser != null) {
            token = jwtUtil.generateToken(sysUser.getName(), String.valueOf(sysUser.getId()));
            // 4. 在Redis中创建或更新玩家会话（Game Session）
            PlayerSessionDto playerSession = PlayerSessionDto.builder()
                    .id(sysUser.getId())
                    .name(sysUser.getName())
                    .nickName(sysUser.getNickName())
                    .phone(sysUser.getPhone())
                    .email(sysUser.getEmail())
                    .lastActiveTime(System.currentTimeMillis())
                    .token(token)
                    .build();
            
            // 将Session对象存入Redis，并设置过期时间
            redisUtils.set(sessionKey, JSONObject.toJSONString(playerSession), 1800);
        }
        return R.ok(token);
    }
    
    /**
     * 使用分布式锁从数据库获取用户信息（带双重检查机制）
     * @param userCacheKey 缓存key
     * @param field 查询字段
     * @param value 查询值
     * @param retryCount 重试次数
     * @return 用户信息
     */
    private SysUser getUserFromDatabaseWithLock(String userCacheKey, String field, String value, int retryCount) {
        SysUser sysUser = null;
        String lockKey = USER_CACHE_LOCK_KEY_PREFIX + value;
        String requestId = UUID.randomUUID().toString();
        
        try {
            // 尝试获取分布式锁，最多等待3秒，持有锁10秒
            if (redisUtils.tryLock(lockKey, requestId, 10, TimeUnit.SECONDS)) {
                try {
                    // 双重检查：获取锁后再检查一次缓存
                    Object cachedUser = redisUtils.get(userCacheKey);
                    if (cachedUser != null) {
                        sysUser = JSONObject.parseObject(cachedUser.toString(), SysUser.class);
                        log.debug("从Redis中获取用户信息（双重检查）: {}", value);
                    } else {
                        // 查询数据库
                        sysUser = this.getOne(new QueryWrapper<SysUser>().eq(field, value));
                        log.debug("从数据库中获取用户信息: {}", value);
                        
                        // 将用户信息存入Redis
                        if (sysUser != null) {
                            redisUtils.set(userCacheKey, JSONObject.toJSONString(sysUser), USER_CACHE_EXPIRE, TimeUnit.SECONDS);
                            log.debug("将用户信息存入Redis: {}", value);
                        }
                    }
                } finally {
                    // 释放锁
                    redisUtils.releaseLock(lockKey, requestId);
                }
            } else {
                // 获取锁失败，线程等待1秒后重试
                if (retryCount < 3) {
                    log.warn("获取分布式锁失败，等待1秒后重试: {} (第{}次)", value, retryCount + 1);
                    Thread.sleep(1000); // 等待1秒
                    
                    // 先检查缓存中是否有数据
                    Object cachedUser = redisUtils.get(userCacheKey);
                    if (cachedUser != null) {
                        sysUser = JSONObject.parseObject(cachedUser.toString(), SysUser.class);
                        log.debug("重试期间从Redis中获取到用户信息: {}", value);
                    } else {
                        // 递归调用继续尝试获取锁
                        sysUser = getUserFromDatabaseWithLock(userCacheKey, field, value, retryCount + 1);
                    }
                } else {
                    // 连续3次获取锁失败，进行降级处理
                    log.warn("连续3次获取分布式锁失败，直接查询数据库: {}", value);
                    sysUser = this.getOne(new QueryWrapper<SysUser>().eq(field, value));
                }
            }
        } catch (Exception e) {
            log.error("获取用户信息时发生异常: {}", value, e);
            // 异常情况下直接查询数据库（降级处理）
            sysUser = this.getOne(new QueryWrapper<SysUser>().eq(field, value));
        }
        
        return sysUser;
    }
    
    @Override
    public R<String> logout(String username) {
        try {
            // 删除基于用户ID的旧会话键
            String oldSessionKey = USER_SESSION_KEY_PREFIX + username;
            redisUtils.delete(oldSessionKey);
            log.info("用户退出登录，用户ID: {}", username);
            return R.ok("退出登录成功");
        } catch (Exception e) {
            log.error("用户退出登录失败，用户ID: {}", username, e);
            return R.fail("退出登录失败");
        }
    }

    public static void main(String[] args) {
        System.out.println(MD5.create().digestHex("sxw123"));
    }
}